package dev.ideas.funkyj.sequence;

import dev.ideas.funkyj.function.F;
import dev.ideas.funkyj.function.P;
import dev.ideas.funkyj.function._Function;
import dev.ideas.funkyj.support.Iterators;
import dev.ideas.funkyj.support.PredicateSupport;

import java.lang.reflect.Array;
import java.util.*;

/**
 */
//todo remove this hierarchy, if possible
public abstract class _Sequence<T> extends PredicateSupport implements Iterable<T> {
    final Iterable<?> from;
    F f = identity;

    private F<_Sequence<T>,Iterator<T>> execution = Executions.simpleLazyExecution();
    public _Sequence(Iterable<?> from) {this.from = from;}
    public static <T> Sequence<T> from(final Iterable<T> xs) { return new Sequence<T>(xs) { }; }
    public static <T> Sequence<T> from(final T... ts) { return from(Arrays.asList(ts)); }
    //
    //todo is it possible to use to(list) instead?
    public List<T> asList() {
        final ArrayList<T> l = new ArrayList<T>();
        for (T t : this) l.add(t);
        return l;
    }

    /**Nullable*/ public T[] asArray() {
        final List<T> ts = asList();
        if (!ts.isEmpty()) {
            T[] r = (T[]) Array.newInstance(ts.get(0).getClass(), ts.size());
            return ts.toArray(r);
        } else {
            return null; //todo is it possible to return an empty array of T? probably not
        }
    }

    public Set<T> asSet() {
        Set<T> set = new HashSet<T>();
        for (T t : this) set.add(t);
        return set;
    }

    //
    //todo try to implement concat as a reduce function over iterables (from(...).to(concatenated))
    public Sequence<T> concat(final Iterable<T>... iterables) {
        return Sequence.from(new Iterable<T>() {
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    Iterator<T> current = _Sequence.this.iterator();
                    int i = 0;
                    public boolean hasNext() {
                        while (!current.hasNext()) {
                            if (i < iterables.length) current = iterables[i++].iterator();
                            else return false;
                        }
                        return true;
                    }
                    public T next() { return current.next(); }
                    public void remove() { current.remove(); }
                };
            }});
    }

    public Sequence<T> concat(final T... ts) {
        return concat(Sequence.from(ts));
    }
    public /*todo final*/ Iterator<T> iterator() { return execution.apply(this); }
    public _Sequence<T> parallel() { setExecution(Executions.<T>parallelAllExecution(Runtime.getRuntime().availableProcessors())); return this; }
    public void setExecution(F<_Sequence<T>, Iterator<T>> execution) { this.execution = execution; }
    //
    public _Sequence<T> where(final P<? super T> p) { f = (f == identity ? maybe(p) : f.on(maybe(p))); return this; }
    public _Sequence<T> where(final P<? super T>... ps) { return where(and(ps)); }
    //
    /*to transform*/
    public <T1> Sequence<T1> to(final F<? super T, T1> t) { f = (f == identity ? maybe(t) : f.on(maybe(t))); return (Sequence<T1>) this; }
    /*to fork*/
    public <T1> Sequence<T1> to(final _Function<T, Iterator<T1>> f) {
        return new Sequence<T1>(
                new Iterable<T1>() {
                    public Iterator<T1> iterator() {
                        return new Iterators.ForkIterator<T1, T>(_Sequence.this.iterator(), f);
                    }
                });
    }
    public T first() {
        Iterator<T> iterator = iterator(); //re-initiate
        return iterator.hasNext() ? iterator.next() : null;
    }

    /**
     * sorts this iterable. if it happens to be an instance of Comparable, it uses it's comparable ability, otherwise a compare by hashcode is used
     */
    //todo sort should be lazy
    public Sequence<T> sort() {
        final List<T> sorted = asList();
        if (!sorted.isEmpty()) {
            final T x = sorted.get(0);
            if (x instanceof Comparable) {
                //noinspection unchecked
                Collections.sort(sorted, natural);
            } else {
                //noinspection unchecked
                Collections.sort(sorted, hashcode);
            }
        }

        return from(sorted);
    }
    //todo sort should be lazy
    public Sequence<T> sort(final Comparator<T> comparator) {
        final List<T> sorted = asList();
        Collections.sort(sorted, comparator);
        return from(sorted);
    }
    @SuppressWarnings({"unchecked"})
    public <Y> Sequence<T> sort(final F<T, Y> f) {
        return sort(new Comparator<T>() {
          public int compare(T o1, T o2) {
            return ((Comparable<Y>) f.of(o1)).compareTo(f.of(o2));
          }
        });
    }
    @Override public String toString() {
        return f.toString();
    }
}
